"""
同步引擎模块
负责实际的文件同步操作
"""
import os
import shutil
import hashlib
import time
from pathlib import Path
from typing import List, Dict, Callable, Optional
import fnmatch


class SyncEngine:
    """文件同步引擎"""
    
    def __init__(self, source_dir: str, target_dirs: List[str], 
                 ignore_patterns: List[str] = None,
                 bidirectional: bool = False,
                 callback: Callable = None,
                 progress_callback: Callable = None):
        """
        初始化同步引擎
        
        Args:
            source_dir: 源目录（主盘）
            target_dirs: 目标目录列表
            ignore_patterns: 忽略的文件模式
            bidirectional: 是否双向同步
            callback: 回调函数，用于报告同步进度
            progress_callback: 进度回调函数，用于更新文件统计
        """
        self.source_dir = os.path.abspath(source_dir)
        self.target_dirs = [os.path.abspath(d) for d in target_dirs]
        self.ignore_patterns = ignore_patterns or []
        self.bidirectional = bidirectional
        self.callback = callback
        self.progress_callback = progress_callback
        self.running = False
        self.synced_files = 0  # 已同步文件数
        self.total_files = 0   # 总文件数
        
    def _log(self, message: str, level: str = "INFO"):
        """记录日志"""
        if self.callback:
            self.callback(message, level)
        else:
            print(f"[{level}] {message}")
    
    def _should_ignore(self, path: str) -> bool:
        """检查是否应该忽略该文件"""
        rel_path = os.path.relpath(path, self.source_dir)
        
        for pattern in self.ignore_patterns:
            # 支持目录模式（以/结尾）
            if pattern.endswith('/'):
                if fnmatch.fnmatch(rel_path + '/', f"*/{pattern}") or \
                   fnmatch.fnmatch(rel_path + '/', pattern):
                    return True
            # 支持文件模式
            elif fnmatch.fnmatch(os.path.basename(path), pattern) or \
                 fnmatch.fnmatch(rel_path, pattern):
                return True
        
        return False
    
    def _get_file_hash(self, filepath: str) -> Optional[str]:
        """计算文件的MD5哈希值"""
        try:
            hash_md5 = hashlib.md5()
            with open(filepath, "rb") as f:
                for chunk in iter(lambda: f.read(4096), b""):
                    hash_md5.update(chunk)
            return hash_md5.hexdigest()
        except Exception as e:
            self._log(f"计算文件哈希失败 {filepath}: {e}", "ERROR")
            return None
    
    def _files_are_different(self, source_file: str, target_file: str) -> bool:
        """比较两个文件是否不同"""
        # 如果目标文件不存在，则不同
        if not os.path.exists(target_file):
            return True
        
        # 首先比较文件大小
        if os.path.getsize(source_file) != os.path.getsize(target_file):
            return True
        
        # 如果大小相同，比较修改时间
        source_mtime = os.path.getmtime(source_file)
        target_mtime = os.path.getmtime(target_file)
        
        # 如果修改时间相差超过1秒，认为不同
        if abs(source_mtime - target_mtime) > 1:
            return True
        
        return False
    
    def _copy_file(self, source_file: str, target_file: str):
        """复制文件"""
        try:
            # 确保目标目录存在
            os.makedirs(os.path.dirname(target_file), exist_ok=True)
            
            # 复制文件
            shutil.copy2(source_file, target_file)
            self._log(f"已复制: {source_file} -> {target_file}")
        except Exception as e:
            self._log(f"复制文件失败 {source_file}: {e}", "ERROR")
    
    def _delete_file(self, filepath: str):
        """删除文件"""
        try:
            if os.path.isfile(filepath):
                os.remove(filepath)
                self._log(f"已删除文件: {filepath}")
            elif os.path.isdir(filepath):
                shutil.rmtree(filepath)
                self._log(f"已删除目录: {filepath}")
        except Exception as e:
            self._log(f"删除失败 {filepath}: {e}", "ERROR")
    
    def sync_file(self, source_file: str):
        """同步单个文件到所有目标目录"""
        if not os.path.exists(source_file):
            self._log(f"源文件不存在: {source_file}", "WARNING")
            return
        
        if self._should_ignore(source_file):
            return
        
        # 获取相对路径
        rel_path = os.path.relpath(source_file, self.source_dir)
        
        # 同步到所有目标目录
        file_copied = False
        for target_dir in self.target_dirs:
            target_file = os.path.join(target_dir, rel_path)
            
            # 检查是否需要复制
            if self._files_are_different(source_file, target_file):
                self._copy_file(source_file, target_file)
                file_copied = True
        
        # 统计同步文件数
        if file_copied:
            self.synced_files += 1
    
    def sync_delete(self, deleted_path: str):
        """同步删除操作到所有目标目录"""
        rel_path = os.path.relpath(deleted_path, self.source_dir)
        
        for target_dir in self.target_dirs:
            target_path = os.path.join(target_dir, rel_path)
            if os.path.exists(target_path):
                self._delete_file(target_path)
    
    def full_sync(self):
        """执行完整的同步"""
        self.running = True
        self.synced_files = 0  # 重置统计
        self._log("=== 开始完整同步 ===")
        start_time = time.time()
        
        try:
            # 1. 统计总文件数并同步
            self._log("正在扫描并同步源目录...")
            self.total_files = 0
            file_count = 0
            for root, dirs, files in os.walk(self.source_dir):
                # 过滤需要忽略的目录
                dirs[:] = [d for d in dirs if not self._should_ignore(os.path.join(root, d))]
                
                for file in files:
                    if not self.running:
                        self._log("同步已取消", "WARNING")
                        return
                    
                    source_file = os.path.join(root, file)
                    
                    if self._should_ignore(source_file):
                        continue
                    
                    self.total_files += 1
                    self.sync_file(source_file)
                    file_count += 1
                    
                    # 每处理10个文件更新一次进度
                    if file_count % 10 == 0 and self.progress_callback:
                        try:
                            self.progress_callback(self.total_files, self.synced_files)
                        except Exception:
                            pass
            
            # 2. 清理目标目录中多余的文件
            for target_dir in self.target_dirs:
                if not self.running:
                    break
                
                self._log(f"正在清理目标目录: {target_dir}")
                self._clean_target_dir(target_dir)
            
            elapsed_time = time.time() - start_time
            self._log(f"=== 完整同步完成 === 耗时: {elapsed_time:.2f}秒, 总文件: {self.total_files}, 同步: {self.synced_files}")
            
            # 最后更新一次进度
            if self.progress_callback:
                try:
                    self.progress_callback(self.total_files, self.synced_files)
                except Exception:
                    pass
            
        except Exception as e:
            self._log(f"完整同步失败: {e}", "ERROR")
        finally:
            self.running = False
    
    def _clean_target_dir(self, target_dir: str):
        """清理目标目录中源目录不存在的文件"""
        try:
            for root, dirs, files in os.walk(target_dir):
                rel_root = os.path.relpath(root, target_dir)
                source_root = os.path.join(self.source_dir, rel_root) if rel_root != '.' else self.source_dir
                
                # 检查目录是否在源目录中存在
                if not os.path.exists(source_root):
                    self._delete_file(root)
                    continue
                
                # 检查文件是否在源目录中存在
                for file in files:
                    target_file = os.path.join(root, file)
                    rel_path = os.path.relpath(target_file, target_dir)
                    source_file = os.path.join(self.source_dir, rel_path)
                    
                    if not os.path.exists(source_file):
                        self._delete_file(target_file)
        
        except Exception as e:
            self._log(f"清理目标目录失败 {target_dir}: {e}", "ERROR")
    
    def stop(self):
        """停止同步"""
        self.running = False
        self._log("正在停止同步...")


